package db

import (
	"database/sql"
	"fmt"
	"gocommon/config"
	"gocommon/log"
	"reflect"
	"strconv"

	_ "github.com/go-sql-driver/mysql"
)

type Mysql struct {
	db     *sql.DB
	addr   string
	user   string
	passwd string
	dbname string
	pool   int
}

var (
	_mainMysql *Mysql
)

func InitMysql() {
	var (
		addr, user, passwd, dbname string
		pool                       int
	)

	err := config.Get("mysql:main:addr", &addr)
	if nil != err {
		log.Fatalln(err)
	}

	err = config.Get("mysql:main:user", &user)
	if nil != err {
		log.Fatalln(err)
	}

	err = config.Get("mysql:main:passwd", &passwd)
	if nil != err {
		log.Fatalln(err)
	}

	err = config.Get("mysql:main:dbname", &dbname)
	if nil != err {
		log.Fatalln(err)
	}

	err = config.Get("mysql:main:pool", &pool)
	if nil != err {
		log.Fatalln(err)
	}

	_mainMysql, err = newMysql(addr, user, passwd, dbname, pool)
	if nil != err {
		log.Fatalln(err)
	}
}

func openDB(addr, user, passwd, dbname string, pool int) (*sql.DB, error) {
	dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4", user, passwd, addr, dbname)
	db, err := sql.Open("mysql", dsn)
	if nil != err {
		log.Errorln(err)
		return nil, err
	}
	db.SetMaxIdleConns(pool)
	db.SetMaxOpenConns(pool)

	return db, nil
}

func newMysql(addr, user, passwd, dbname string, pool int) (*Mysql, error) {
	db, err := openDB(addr, user, passwd, dbname, pool)
	if nil != err {
		log.Errorln(err)
		return nil, err
	}

	return &Mysql{
		db:     db,
		addr:   addr,
		user:   user,
		passwd: passwd,
		dbname: dbname,
		pool:   pool,
	}, nil
}

func MainDB() *Mysql {
	return _mainMysql
}

func (m *Mysql) reGetDBConn() error {
	var i int = 0
	var err error = nil
	var db *sql.DB

	//重试三次链接数据库
	for ; i < 3; i++ {
		db, err = openDB(m.addr, m.user, m.passwd, m.passwd, m.pool)
		if nil != err {
			log.Warnln(err)
			return err
		}
		break
	}

	if i >= 3 {
		log.Errorf("try %d,but failed,err:%+v", i, err)
		return err
	}

	m.db = db

	return nil
}

func (m *Mysql) getDB() (*sql.DB, error) {
	if nil == m.db {
		err := m.reGetDBConn()
		if nil != err {
			log.Errorln(err)
			return nil, err
		}
	}

	err := m.db.Ping()
	if nil != err {
		err := m.reGetDBConn()
		if nil != err {
			log.Errorln(err)
			return nil, err
		}
	}

	return m.db, nil
}

func (m *Mysql) Query(query string, args ...interface{}) (*sql.Rows, error) {
	db, err := m.getDB()
	if nil != err {
		log.Errorln(err)
		return nil, err
	}
	sqlStr := fmt.Sprintf(query, args...)
	log.Debugln(sqlStr)

	return db.Query(sqlStr)
}

func (m *Mysql) QueryRow(query string, args ...interface{}) *sql.Row {
	db, err := m.getDB()
	if nil != err {
		log.Errorln(err)
		return nil
	}
	sqlStr := fmt.Sprintf(query, args...)
	log.Debugln(sqlStr)

	return db.QueryRow(sqlStr)
}

func (m *Mysql) GetAll(v interface{}, query string, args ...interface{}) error {
	rows, err := m.doGetAll(query, args...)
	if nil != err {
		log.Errorln(err)
		return err
	}
	if 0 == len(rows) {
		log.Warnln("not found")
		return fmt.Errorf("not found")
	}

	val := reflect.ValueOf(v)
	length := len(rows)
	for i := 0; i < length; i++ {
		val2 := val.Index(i)
		err = m.doGetOne(rows[i], val2)
		if nil != err {
			log.Errorln(err)
			return err
		}
	}
	return nil
}

func (m *Mysql) doGetAll(query string, args ...interface{}) ([]map[string]string, error) {
	rows, err := m.Query(query, args...)
	if nil != err {
		log.Errorln(err)
		return nil, err
	}

	columns, _ := rows.Columns()
	scanArgs := make([]interface{}, len(columns))
	values := make([]interface{}, len(columns))
	for j := range values {
		scanArgs[j] = &values[j]
	}

	record := make([]map[string]string, 0)
	for rows.Next() {
		//将行数据保存到record字典
		err = rows.Scan(scanArgs...)
		m := map[string]string{}
		for i, col := range values {
			if col != nil {
				m[columns[i]] = string(col.([]byte))
			}
		}

		record = append(record, m)
	}

	log.Debugf("%+v\n", record)

	return record, nil
}

func (m *Mysql) GetOne(v interface{}, query string, args ...interface{}) error {
	rows, err := m.doGetAll(query, args...)
	if nil != err {
		log.Errorln(err)
		return err
	}
	if 0 == len(rows) {
		log.Warnln("not found")
		return fmt.Errorf("not found")
	}

	row := rows[0]

	return m.doGetOne(row, v)
}

func (m *Mysql) doGetOne(row map[string]string, v interface{}) error {
	tp := reflect.TypeOf(v)
	val := reflect.ValueOf(v)

	length := tp.Elem().NumField()
	for i := 0; i < length; i++ {
		dbTag := tp.Elem().Field(i).Tag.Get("db")
		if "" == dbTag || "-" == dbTag {
			continue
		}

		val2, ok := row[dbTag]
		if !ok {
			continue
		}

		switch tp.Elem().Field(i).Type.Kind() {
		case reflect.String:
			val.Elem().Field(i).SetString(val2)
		case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int16:
			val3, err := strconv.ParseInt(val2, 10, 64)
			if nil != err {
				log.Errorln(err)
				continue
			}
			val.Elem().Field(i).SetInt(val3)
		case reflect.Float32, reflect.Float64:
			val3, err := strconv.ParseFloat(val2, 64)
			if nil != err {
				log.Errorln(err)
				continue
			}
			val.Elem().Field(i).SetFloat(val3)
		case reflect.Bool:
			val3, err := strconv.ParseBool(val2)
			if nil != err {
				log.Errorln(err)
				continue
			}
			val.Elem().Field(i).SetBool(val3)
		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
			val3, err := strconv.ParseUint(val2, 10, 64)
			if nil != err {
				log.Errorln(err)
				continue
			}
			val.Elem().Field(i).SetUint(val3)
		default:
		}
	}

	return nil
}
